{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fcfcb6c4",
   "metadata": {},
   "source": [
    "# TypeScript 三次曲线插值\n",
    "\n",
    "以下是使用 TypeScript 和 math.js 实现的三次曲线插值方法：\n",
    "\n",
    "## 0. 准备工作\n",
    "\n",
    "1. 安装 TypeScript 编译器：`npm install -g typescript`\n",
    "2. 安装 math.js：`npm install math.js` 或者 `yarn add math.js`\n",
    "\n",
    "## 1. 三次贝塞尔曲线插值\n",
    "\n",
    "```typescript\n",
    "/​**​\n",
    " * 三次贝塞尔曲线插值\n",
    " * @param p0 - 起点\n",
    " * @param p1 - 控制点1\n",
    " * @param p2 - 控制点2\n",
    " * @param p3 - 终点\n",
    " * @param t - 插值参数 (0到1之间)\n",
    " * @returns 插值结果\n",
    " */\n",
    "function cubicBezier(p0: number, p1: number, p2: number, p3: number, t: number): number {\n",
    "    const mt = 1 - t;\n",
    "    return mt*mt*mt*p0 + 3*mt*mt*t*p1 + 3*mt*t*t*p2 + t*t*t*p3;\n",
    "}\n",
    "\n",
    "// 示例使用\n",
    "const start = 0;\n",
    "const control1 = 20;\n",
    "const control2 = 80;\n",
    "const end = 100;\n",
    "\n",
    "for (let t = 0; t <= 1; t += 0.1) {\n",
    "    console.log(`t=${t.toFixed(1)}: ${cubicBezier(start, control1, control2, end, t)}`);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aaae99fa",
   "metadata": {},
   "source": [
    "\n",
    "## 2. 三次样条插值\n",
    "\n",
    "```typescript\n",
    "import * as math from 'mathjs';\n",
    "\n",
    "interface Point {\n",
    "    x: number;\n",
    "    y: number;\n",
    "}\n",
    "\n",
    "/​**​\n",
    " * 三次样条插值（使用math.js）\n",
    " * @param points - 包含{x, y}对象的数组\n",
    " * @param x - 要插值的x坐标\n",
    " * @returns 插值得到的y值\n",
    " */\n",
    "function cubicSplineInterpolation(points: Point[], x: number): number {\n",
    "    // 对点按x坐标排序\n",
    "    points.sort((a, b) => a.x - b.x);\n",
    "    \n",
    "    // 找到x所在的区间\n",
    "    let i = 0;\n",
    "    for (; i < points.length - 1; i++) {\n",
    "        if (x >= points[i].x && x <= points[i+1].x) break;\n",
    "    }\n",
    "    \n",
    "    if (i >= points.length - 1) {\n",
    "        return points[points.length - 1].y;\n",
    "    }\n",
    "    \n",
    "    // 使用自然三次样条条件（二阶导数在端点处为0）\n",
    "    const n = points.length;\n",
    "    const h: number[] = [];  // 区间长度\n",
    "    const alpha: number[] = [];  // 中间变量\n",
    "    const l: number[] = [];  // 对角线\n",
    "    const mu: number[] = [];  // \n",
    "    const z: number[] = [];  // \n",
    "    const c: number[] = new Array(n).fill(0);  // 二阶导数\n",
    "    \n",
    "    // 1. 计算h和alpha\n",
    "    for (let j = 0; j < n-1; j++) {\n",
    "        h[j] = points[j+1].x - points[j].x;\n",
    "        if (j > 0) {\n",
    "            alpha[j] = (3/h[j])*(points[j+1].y - points[j].y) - (3/h[j-1])*(points[j].y - points[j-1].y);\n",
    "        }\n",
    "    }\n",
    "    \n",
    "    // 2. 设置自然样条边界条件\n",
    "    l[0] = 1;\n",
    "    mu[0] = 0;\n",
    "    z[0] = 0;\n",
    "    \n",
    "    // 3. 计算l, mu, z\n",
    "    for (let j = 1; j < n-1; j++) {\n",
    "        l[j] = 2*(points[j+1].x - points[j-1].x) - h[j-1]*mu[j-1];\n",
    "        mu[j] = h[j] / l[j];\n",
    "        z[j] = (alpha[j] - h[j-1]*z[j-1]) / l[j];\n",
    "    }\n",
    "    \n",
    "    // 4. 设置边界条件\n",
    "    l[n-1] = 1;\n",
    "    z[n-1] = 0;\n",
    "    c[n-1] = 0;\n",
    "    \n",
    "    // 5. 计算c (二阶导数)\n",
    "    for (let j = n-2; j >= 0; j--) {\n",
    "        c[j] = z[j] - mu[j]*c[j+1];\n",
    "    }\n",
    "    \n",
    "    // 6. 计算b和d系数\n",
    "    const b: number[] = [];\n",
    "    const d: number[] = [];\n",
    "    for (let j = 0; j < n-1; j++) {\n",
    "        b[j] = (points[j+1].y - points[j].y)/h[j] - h[j]*(c[j+1] + 2*c[j])/3;\n",
    "        d[j] = (c[j+1] - c[j])/(3*h[j]);\n",
    "    }\n",
    "    \n",
    "    // 7. 计算插值\n",
    "    const j = i;\n",
    "    const dx = x - points[j].x;\n",
    "    return points[j].y + b[j]*dx + c[j]*dx*dx + d[j]*dx*dx*dx;\n",
    "}\n",
    "\n",
    "// 示例使用\n",
    "const splinePoints: Point[] = [\n",
    "    {x: 0, y: 0},\n",
    "    {x: 1, y: 2},\n",
    "    {x: 2, y: 1},\n",
    "    {x: 3, y: 3}\n",
    "];\n",
    "\n",
    "for (let x = 0; x <= 3; x += 0.2) {\n",
    "    const y = cubicSplineInterpolation(splinePoints, x);\n",
    "    console.log(`x=${x.toFixed(1)}: y=${y.toFixed(2)}`);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15377fa0",
   "metadata": {},
   "source": [
    "## 3. Canvas中的三次曲线绘制\n",
    "\n",
    "```typescript\n",
    "interface Point2D {\n",
    "    x: number;\n",
    "    y: number;\n",
    "}\n",
    "\n",
    "/​**​\n",
    " * 在Canvas中绘制三次贝塞尔曲线\n",
    " * @param ctx - Canvas渲染上下文\n",
    " * @param p0 - 起点\n",
    " * @param p1 - 控制点1\n",
    " * @param p2 - 控制点2\n",
    " * @param p3 - 终点\n",
    " */\n",
    "function drawCubicCurve(\n",
    "    ctx: CanvasRenderingContext2D,\n",
    "    p0: Point2D,\n",
    "    p1: Point2D,\n",
    "    p2: Point2D,\n",
    "    p3: Point2D\n",
    "): void {\n",
    "    ctx.beginPath();\n",
    "    ctx.moveTo(p0.x, p0.y);\n",
    "    ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);\n",
    "    ctx.stroke();\n",
    "}\n",
    "\n",
    "// 使用示例\n",
    "const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;\n",
    "if (canvas) {\n",
    "    const ctx = canvas.getContext('2d');\n",
    "    if (ctx) {\n",
    "        const startPoint: Point2D = {x: 50, y: 200};\n",
    "        const controlPoint1: Point2D = {x: 100, y: 50};\n",
    "        const controlPoint2: Point2D = {x: 200, y: 50};\n",
    "        const endPoint: Point2D = {x: 250, y: 200};\n",
    "\n",
    "        drawCubicCurve(ctx, startPoint, controlPoint1, controlPoint2, endPoint);\n",
    "    }\n",
    "}\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e25da8e2",
   "metadata": {},
   "source": [
    "## 4. 动画中的三次插值\n",
    "\n",
    "```typescript\n",
    "/​**​\n",
    " * 使用三次插值创建平滑动画\n",
    " * @param start - 起始值\n",
    " * @param end - 结束值\n",
    " * @param duration - 动画持续时间(毫秒)\n",
    " * @param callback - 每帧回调函数\n",
    " */\n",
    "function animateWithCubic(\n",
    "    start: number,\n",
    "    end: number,\n",
    "    duration: number,\n",
    "    callback: (value: number) => void\n",
    "): void {\n",
    "    const startTime = performance.now();\n",
    "    const control1 = start + (end - start) * 0.3; // 第一个控制点\n",
    "    const control2 = start + (end - start) * 0.7; // 第二个控制点\n",
    "    \n",
    "    function update(currentTime: number): void {\n",
    "        const elapsed = currentTime - startTime;\n",
    "        const t = Math.min(elapsed / duration, 1);\n",
    "        \n",
    "        // 使用三次贝塞尔插值\n",
    "        const value = cubicBezier(start, control1, control2, end, t);\n",
    "        \n",
    "        callback(value);\n",
    "        \n",
    "        if (t < 1) {\n",
    "            requestAnimationFrame(update);\n",
    "        }\n",
    "    }\n",
    "    \n",
    "    requestAnimationFrame(update);\n",
    "}\n",
    "\n",
    "// 使用示例\n",
    "animateWithCubic(0, 100, 1000, (value) => {\n",
    "    console.log(value);\n",
    "    // 更新DOM元素位置等\n",
    "});\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fd2614f",
   "metadata": {},
   "source": [
    "## 5. 三次多项式插值\n",
    "\n",
    "```typescript\n",
    "import * as math from 'mathjs';\n",
    "\n",
    "interface Point {\n",
    "    x: number;\n",
    "    y: number;\n",
    "}\n",
    "\n",
    "/​**​\n",
    " * 三次多项式插值（通过4个点确定，使用math.js）\n",
    " * @param points - 包含4个{x, y}对象的数组\n",
    " * @param x - 要插值的x坐标\n",
    " * @returns 插值得到的y值\n",
    " */\n",
    "function cubicPolynomialInterpolation(points: Point[], x: number): number {\n",
    "    if (points.length !== 4) {\n",
    "        throw new Error(\"需要4个点进行三次多项式插值\");\n",
    "    }\n",
    "    \n",
    "    // 构建矩阵方程：求解 y = ax³ + bx² + cx + d\n",
    "    const A = math.matrix([\n",
    "        [math.pow(points[0].x, 3), math.pow(points[0].x, 2), points[0].x, 1],\n",
    "        [math.pow(points[1].x, 3), math.pow(points[1].x, 2), points[1].x, 1],\n",
    "        [math.pow(points[2].x, 3), math.pow(points[2].x, 2), points[2].x, 1],\n",
    "        [math.pow(points[3].x, 3), math.pow(points[3].x, 2), points[3].x, 1]\n",
    "    ]);\n",
    "    \n",
    "    // 构建结果向量\n",
    "    const b = math.matrix([\n",
    "        [points[0].y],\n",
    "        [points[1].y],\n",
    "        [points[2].y],\n",
    "        [points[3].y]\n",
    "    ]);\n",
    "    \n",
    "    // 解线性方程组\n",
    "    const coefficients = math.lusolve(A, b);\n",
    "    \n",
    "    // 提取系数\n",
    "    const a = coefficients.get([0, 0]);\n",
    "    const b_coef = coefficients.get([1, 0]);\n",
    "    const c = coefficients.get([2, 0]);\n",
    "    const d = coefficients.get([3, 0]);\n",
    "    \n",
    "    // 计算插值\n",
    "    return a * math.pow(x, 3) + b_coef * math.pow(x, 2) + c * x + d;\n",
    "}\n",
    "\n",
    "// 示例使用\n",
    "const interpolationPoints: Point[] = [\n",
    "    {x: 0, y: 0},\n",
    "    {x: 1, y: 2},\n",
    "    {x: 2, y: 1},\n",
    "    {x: 3, y: 3}\n",
    "];\n",
    "\n",
    "for (let x = 0; x <= 3; x += 0.2) {\n",
    "    const y = cubicPolynomialInterpolation(interpolationPoints, x);\n",
    "    console.log(`x=${x.toFixed(1)}: y=${y.toFixed(2)}`);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64d5d499",
   "metadata": {},
   "source": [
    "## 6. 使用 math.js 的简化样条插值\n",
    "\n",
    "```typescript\n",
    "import * as math from 'mathjs';\n",
    "\n",
    "interface Point {\n",
    "    x: number;\n",
    "    y: number;\n",
    "}\n",
    "\n",
    "/​**​\n",
    " * 简化三次样条插值（使用math.js）\n",
    " * @param points - 包含{x, y}对象的数组\n",
    " * @param x - 要插值的x坐标\n",
    " * @returns 插值得到的y值\n",
    " */\n",
    "function simpleCubicSpline(points: Point[], x: number): number {\n",
    "    points.sort((a, b) => a.x - b.x);\n",
    "    \n",
    "    let i = 0;\n",
    "    for (; i < points.length - 1; i++) {\n",
    "        if (x >= points[i].x && x <= points[i+1].x) break;\n",
    "    }\n",
    "    \n",
    "    if (i >= points.length - 1) return points[points.length - 1].y;\n",
    "    \n",
    "    // 使用Catmull-Rom样条\n",
    "    const p0 = i > 0 ? points[i-1].y : points[i].y;\n",
    "    const p1 = points[i].y;\n",
    "    const p2 = points[i+1].y;\n",
    "    const p3 = i < points.length - 2 ? points[i+2].y : points[i+1].y;\n",
    "    \n",
    "    const t = (x - points[i].x) / (points[i+1].x - points[i].x);\n",
    "    \n",
    "    // 使用math.js进行向量化计算\n",
    "    const T = math.matrix([1, t, math.pow(t, 2), math.pow(t, 3)]);\n",
    "    const M = math.matrix([\n",
    "        [0, 1, 0, 0],\n",
    "        [-0.5, 0, 0.5, 0],\n",
    "        [1, -2.5, 2, -0.5],\n",
    "        [-0.5, 1.5, -1.5, 0.5]\n",
    "    ]);\n",
    "    const P = math.matrix([[p0], [p1], [p2], [p3]]);\n",
    "    \n",
    "    const result = math.multiply(math.multiply(T, M), P);\n",
    "    return result.get([0, 0]);\n",
    "}\n",
    "\n",
    "// 示例使用\n",
    "const splinePoints: Point[] = [\n",
    "    {x: 0, y: 0},\n",
    "    {x: 1, y: 2},\n",
    "    {x: 2, y: 1},\n",
    "    {x: 3, y: 3}\n",
    "];\n",
    "\n",
    "for (let x = 0; x <= 3; x += 0.2) {\n",
    "    const y = simpleCubicSpline(splinePoints, x);\n",
    "    console.log(`x=${x.toFixed(1)}: y=${y.toFixed(2)}`);\n",
    "}\n",
    "```\t\n"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
